package com.grandtech.insurance.common.core.coding;


import com.google.protobuf.nano.MessageNano;
import com.grandtech.insurance.common.core.tile.VectorTile;
import com.grandtech.insurance.common.core.tile.VectorTile.Tile;
import com.grandtech.insurance.common.core.tile.VectorTile.Tile.Value;
import com.vividsolutions.jts.algorithm.CGAlgorithms;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.util.AssertionFailedException;

import java.util.*;
import java.util.Map.Entry;


public class VectorTileEncoder {
    private final Map<String, Layer> layers;
    private final int extent;
    private final Geometry clipGeometry;
    private final boolean autoScale;
    private int x;
    private int y;

    public VectorTileEncoder() {
        this(4096, 8, true);
    }

    public VectorTileEncoder(int extent) {
        this(extent, 8, true);
    }

    public VectorTileEncoder(int extent, int clipBuffer, boolean autoScale) {
        this.layers = new LinkedHashMap();
        this.x = 0;
        this.y = 0;
        this.extent = extent;
        this.autoScale = autoScale;
        int size = autoScale ? 256 : extent;
        this.clipGeometry = createTileEnvelope(clipBuffer, size);
    }

    private static Geometry createTileEnvelope(int buffer, int size) {
        Coordinate[] coords = new Coordinate[]{new Coordinate((double) (0 - buffer), (double) (size + buffer)), new Coordinate((double) (size + buffer), (double) (size + buffer)), new Coordinate((double) (size + buffer), (double) (0 - buffer)), new Coordinate((double) (0 - buffer), (double) (0 - buffer)), null};
        coords[4] = coords[0];
        return (new GeometryFactory()).createPolygon(coords);
    }

    public void addFeature(VectorTileDecoder.Feature feature) {
        addFeature(feature.getLayerName(), feature.getAttributes(), feature.getGeometry());
    }

    public void addFeature(String layerName, Map<String, ?> attributes, Geometry geometry) {
        if (!(geometry instanceof MultiPolygon) && !geometry.getClass().equals(GeometryCollection.class)) {
            if (!(geometry instanceof Polygon) || geometry.getArea() >= 1.0D) {
                if (!(geometry instanceof LineString) || geometry.getLength() >= 1.0D) {
                    if (geometry instanceof Point) {
                        if (!this.clipCovers(geometry)) {
                            return;
                        }
                    } else {
                        geometry = this.clipGeometry(geometry);
                    }

                    if (!(geometry instanceof MultiPolygon) && !geometry.getClass().equals(GeometryCollection.class)) {
                        if (!geometry.isEmpty()) {
                            Layer layer = this.layers.get(layerName);
                            if (layer == null) {
                                layer = new Layer();
                                this.layers.put(layerName, layer);
                            }

                            Feature feature = new Feature();
                            feature.geometry = geometry;
                            Iterator var7 = attributes.entrySet().iterator();

                            while (var7.hasNext()) {
                                Entry<String, ?> e = (Entry) var7.next();
                                if (e.getValue() != null) {
                                    feature.tags.add(layer.key(e.getKey()));
                                    feature.tags.add(layer.value(e.getValue()));
                                }
                            }
                            layer.features.add(feature);
                        }
                    } else {
                        this.splitAndAddFeatures(layerName, attributes, (GeometryCollection) geometry);
                    }
                }
            }
        } else {
            this.splitAndAddFeatures(layerName, attributes, (GeometryCollection) geometry);
        }
    }

    protected boolean clipCovers(Geometry geom) {
        if (geom instanceof Point) {
            Point p = (Point) geom;
            return this.clipGeometry.getEnvelopeInternal().covers(p.getCoordinate());
        } else {
            return this.clipGeometry.covers(geom);
        }
    }

    protected Geometry clipGeometry(Geometry geometry) {
        try {
            Geometry original = geometry;
            geometry = this.clipGeometry.intersection(geometry);
            if (geometry.isEmpty() && original.intersects(this.clipGeometry)) {
                Geometry originalViaWkt = (new WKTReader()).read(original.toText());
                geometry = this.clipGeometry.intersection(originalViaWkt);
            }

            return geometry;
        } catch (TopologyException var4) {
            return geometry;
        } catch (ParseException var5) {
            return geometry;
        } catch (AssertionFailedException var6) {
            return geometry;
        }
    }

    private void splitAndAddFeatures(String layerName, Map<String, ?> attributes, GeometryCollection geometry) {
        for (int i = 0; i < geometry.getNumGeometries(); ++i) {
            Geometry subGeometry = geometry.getGeometryN(i);
            this.addFeature(layerName, attributes, subGeometry);
        }

    }

    public byte[] encode() {
        Tile tile = new Tile();
        List<VectorTile.Tile.Layer> tileLayers = new ArrayList();
        Iterator var4 = this.layers.entrySet().iterator();

        while (var4.hasNext()) {
            Entry<String, Layer> e = (Entry) var4.next();
            String layerName = e.getKey();
            Layer layer = e.getValue();
            VectorTile.Tile.Layer tileLayer = new VectorTile.Tile.Layer();
            tileLayer.version = 2;
            tileLayer.name = layerName;
            tileLayer.keys = layer.keys();
            List<Value> values = new ArrayList();

            Value tileValue;
            for (Iterator var10 = layer.values().iterator(); var10.hasNext(); values.add(tileValue)) {
                Object value = var10.next();
                tileValue = new Value();
                if (value instanceof String) {
                    tileValue.setStringValue((String) value);
                } else if (value instanceof Integer) {
                    tileValue.setSintValue((long) ((Integer) value).intValue());
                } else if (value instanceof Long) {
                    tileValue.setSintValue(((Long) value).longValue());
                } else if (value instanceof Float) {
                    tileValue.setFloatValue(((Float) value).floatValue());
                } else if (value instanceof Double) {
                    tileValue.setDoubleValue(((Double) value).doubleValue());
                } else {
                    tileValue.setStringValue(value.toString());
                }
            }

            tileLayer.values = values.toArray(new Value[values.size()]);
            tileLayer.setExtent(this.extent);
            List<VectorTile.Tile.Feature> features = new ArrayList();
            Iterator var16 = layer.features.iterator();

            while (var16.hasNext()) {
                Feature feature = (Feature) var16.next();
                Geometry geometry = feature.geometry;
                VectorTile.Tile.Feature featureBuilder = new VectorTile.Tile.Feature();
                featureBuilder.tags = toIntArray(feature.tags);
                featureBuilder.setType(toGeomType(geometry));
                featureBuilder.geometry = toIntArray(this.commands(geometry));
                features.add(featureBuilder);
            }

            tileLayer.features = features.toArray(new VectorTile.Tile.Feature[features.size()]);
            tileLayers.add(tileLayer);
        }

        tile.layers = tileLayers.toArray(new VectorTile.Tile.Layer[tileLayers.size()]);
        return MessageNano.toByteArray(tile);
    }

    static int[] toIntArray(List<Integer> ints) {
        int[] r = new int[ints.size()];

        for (int i = 0; i < ints.size(); ++i) {
            r[i] = ((Integer) ints.get(i)).intValue();
        }

        return r;
    }

    static int toGeomType(Geometry geometry) {
        if (geometry instanceof Point) {
            return 1;
        } else if (geometry instanceof MultiPoint) {
            return 1;
        } else if (geometry instanceof LineString) {
            return 2;
        } else if (geometry instanceof MultiLineString) {
            return 2;
        } else {
            return geometry instanceof Polygon ? 3 : 0;
        }
    }

    static boolean shouldClosePath(Geometry geometry) {
        return geometry instanceof Polygon || geometry instanceof LinearRing;
    }

    List<Integer> commands(Geometry geometry) {
        this.x = 0;
        this.y = 0;
        if (geometry instanceof Polygon) {
            Polygon polygon = (Polygon) geometry;
            List<Integer> commands = new ArrayList();
            LineString exteriorRing = polygon.getExteriorRing();
            if (!CGAlgorithms.isCCW(exteriorRing.getCoordinates())) {
                exteriorRing = (LineString) exteriorRing.reverse();
            }

            commands.addAll(this.commands(exteriorRing.getCoordinates(), true));

            for (int i = 0; i < polygon.getNumInteriorRing(); ++i) {
                LineString interiorRing = polygon.getInteriorRingN(i);
                if (CGAlgorithms.isCCW(interiorRing.getCoordinates())) {
                    interiorRing = (LineString) interiorRing.reverse();
                }

                commands.addAll(this.commands(interiorRing.getCoordinates(), true));
            }

            return commands;
        } else if (!(geometry instanceof MultiLineString)) {
            return this.commands(geometry.getCoordinates(), shouldClosePath(geometry), geometry instanceof MultiPoint);
        } else {
            List<Integer> commands = new ArrayList();
            GeometryCollection gc = (GeometryCollection) geometry;

            for (int i = 0; i < gc.getNumGeometries(); ++i) {
                commands.addAll(this.commands(gc.getGeometryN(i).getCoordinates(), false));
            }

            return commands;
        }
    }

    List<Integer> commands(Coordinate[] cs, boolean closePathAtEnd) {
        return this.commands(cs, closePathAtEnd, false);
    }

    List<Integer> commands(Coordinate[] cs, boolean closePathAtEnd, boolean multiPoint) {
        if (cs.length == 0) {
            throw new IllegalArgumentException("empty geometry");
        } else {
            List<Integer> r = new ArrayList();
            int lineToIndex = 0;
            int lineToLength = 0;
            double scale = this.autoScale ? (double) this.extent / 256.0D : 1.0D;

            for (int i = 0; i < cs.length; ++i) {
                Coordinate c = cs[i];
                if (i == 0) {
                    r.add(commandAndLength(1, multiPoint ? cs.length : 1));
                }

                int _x = (int) Math.round(c.x * scale);
                int _y = (int) Math.round(c.y * scale);
                if (i > 0 && _x == this.x && _y == this.y) {
                    --lineToLength;
                } else if (closePathAtEnd && cs.length > 1 && i == cs.length - 1 && cs[0].equals(c)) {
                    --lineToLength;
                } else {
                    r.add(zigZagEncode(_x - this.x));
                    r.add(zigZagEncode(_y - this.y));
                    this.x = _x;
                    this.y = _y;
                    if (i == 0 && cs.length > 1 && !multiPoint) {
                        lineToIndex = r.size();
                        lineToLength = cs.length - 1;
                        r.add(commandAndLength(2, lineToLength));
                    }
                }
            }

            if (lineToIndex > 0) {
                if (lineToLength == 0) {
                    r.remove(lineToIndex);
                } else {
                    r.set(lineToIndex, commandAndLength(2, lineToLength));
                }
            }

            if (closePathAtEnd) {
                r.add(commandAndLength(7, 1));
            }

            return r;
        }
    }

    static int commandAndLength(int command, int repeat) {
        return repeat << 3 | command;
    }

    static int zigZagEncode(int n) {
        return n << 1 ^ n >> 31;
    }

    private static final class Feature {
        Geometry geometry;
        final List<Integer> tags;

        private Feature() {
            this.tags = new ArrayList();
        }
    }

    private static final class Layer {
        final List<Feature> features;
        private final Map<String, Integer> keys;
        private final Map<Object, Integer> values;

        private Layer() {
            this.features = new ArrayList();
            this.keys = new LinkedHashMap();
            this.values = new LinkedHashMap();
        }

        public Integer key(String key) {
            Integer i = (Integer) this.keys.get(key);
            if (i == null) {
                i = this.keys.size();
                this.keys.put(key, i);
            }

            return i;
        }

        public String[] keys() {
            List<String> r = new ArrayList(this.keys.keySet());
            return (String[]) r.toArray(new String[r.size()]);
        }

        public Integer value(Object value) {
            Integer i = (Integer) this.values.get(value);
            if (i == null) {
                i = this.values.size();
                this.values.put(value, i);
            }

            return i;
        }

        public List<Object> values() {
            return Collections.unmodifiableList(new ArrayList(this.values.keySet()));
        }
    }

    public void clear() {
        layers.clear();
    }
}
